<?php

namespace app\socketio\controller;

use Workerman\Worker;
use Workerman\Lib\Timer;
use PHPSocketIO\SocketIO;
use think\Db;
use app\api\model\LiveSpeech1;

class Websocket
{
    private $redis;
    private $web_clients;
    private $io;
    public function __construct()
    {
        $this->redis = new \Redis();
        $this->redis->connect(config('huisheng.redis_ip'), config('huisheng.redis_port'));
        $this->redis->setOption(\Redis::OPT_READ_TIMEOUT, -1);

        $this->web_clients = array();
    }

    public function Index()
    {
        $this->io = new SocketIO(12120);

        //创建连接时
        $this->io->on('workerStart', function () {
            Timer::add(0.1, function () {
                $data = json_decode($this->redis->rPop('meeting_transfer'), true);
                $this->io->to($data['mid'])->emit('get audio info', $data['transfer']);
            });

            Timer::add(60, function () {
                $this->checkDisconnection();
            });
        });

        //当有客户端连接
        $this->io->on('connection', function ($socket) {
            try {
                $socket->on('web_login', function ($data) use ($socket) {
                    try {
                        $data = $this->validate($data, $socket);
                        $this->userRegister($data['uid'], $socket);
                    } catch (\Exception $e) {
                        $this->errorMsg($e);
                    }
                });
                $socket->on('bind box', function ($data) use ($socket) {
                    try {
                        $data = $this->validate($data, $socket);
                        if ($this->validateBox($data['did'], $socket)) {
                            $this->bindBox($data['did'], $socket);
                        }
                    } catch (\Exception $e) {
                        $this->errorMsg($e);
                    }
                });
                $socket->on('ping box', function ($data) use ($socket) {
                    try {
                        $data = $this->validate($data, $socket);
                        $this->pingBox($data['did'], $socket);
                    } catch (\Exception $e) {
                        $this->errorMsg($e);
                    }
                });
                $socket->on('command', function ($data) use ($socket) {
                    try {
                        $data = $this->validate($data, $socket);
                        if ($this->validateBox($data['did'], $socket)) {
                            $this->commandBox($data, $socket);
                        }
                    } catch (\Exception $e) {
                        $this->errorMsg($e);
                    }
                });
                $socket->on('get audio history', function ($data) use ($socket) {
                    try {
                        $data = $this->validate($data, $socket);
                        $this->getAudioHistory($data['mid'], $socket);
                    } catch (\Exception $e) {
                        $this->errorMsg($e);
                    }
                });
                $socket->on('edit', function ($data) use ($socket) {
                    try {
                        $data = $this->validate($data, $socket);
                        if ($this->validateBox($data['did'], $socket)) {
                            $this->editResult($data['mid'], $data['did'], json_decode($data['data'], true), $socket);
                        }
                    } catch (\Exception $e) {
                        $this->errorMsg($e);
                    }
                });

                //盒子触发
                $socket->on('boxresponse', function ($data) use ($socket) {
                    try {
                        $data = $this->validate($data, $socket);
                        if ($this->validateBox($socket->did, $socket)) {
                            $data['did'] = $socket->did;
                            $this->responseBox($data);
                        }
                    } catch (\Exception $e) {
                        $this->errorMsg($e);
                    }
                });
                $socket->on('boxlogin', function ($data) use ($socket) {
                    try {
                        $data = $this->validate($data, $socket);
                        $this->loginBox($data['box_id'], $socket);
                    } catch (\Exception $e) {
                        $this->errorMsg($e);
                    }
                });
                $socket->on('boxlogout', function ($data) use ($socket) {
                    try {
                        $did = $socket->did;
                        $this->logoutBox($did, $socket);
                    } catch (\Exception $e) {
                        $this->errorMsg($e);
                    }
                });
                $socket->on('boxtranstatus', function ($data) use ($socket) {
                    try {
                        $data = $this->validate($data, $socket);
                        $this->fileListBox($data, $socket);
                    } catch (\Exception $e) {
                        $this->errorMsg($e);
                    }
                });
                $socket->on('disconnect', function ($data) use ($socket) {
                    if (!isset($socket->did)) {
                        return;
                    }
                    if (!$this->redis->exists('con_box_' . $socket->did)) {
                        return;
                    }
                    $device = json_decode($this->redis->get('con_box_' . $socket->did), true);
                    if ($socket->sgin != $device['sgin']) {
                        return;
                    }
                    //如果断开的盒子的状态不是2，则加入断线队列，并设置该盒子状态生命周期
                    if ($device['status'] != 2) {
                        $result = Db::name('equipment')->where('equipmentid', $socket->did)->update(['connection' => 2, 'update_time' => time()]);
                        //更改原数据名字
                        $this->redis->rename('con_box_' . $socket->did, 'dis_box_' . $socket->did);
                        //添加超时时间
                        $this->redis->expire('dis_box_' . $socket->did, config('huisheng.speech_timeout'));
                        //加入
                        $this->redis->hSet('disconnection', $socket->did, $device['mid']);
                    } else {
                        $this->redis->del('con_box_' . $socket->did);
                    }
                    $this->io->to("box2user-" . $socket->did)->emit('speech status', array('token' => "rjjgdzjmbsyzxdrlcblmzgjhwhgxtdxq", 'status' => 0, 'code' => '0000'));
                    $this->echoWord("盒子 $socket->did 异常断开连接");
                });
            } catch (\Exception $e) {
                var_dump($e);
            }
        });

        Worker::runAll();
    }

    //验证状态
    private function validate($data, $socket)
    {
        try {
            if (!($res = json_decode($data, 1))) {
                $socket->emit('exception', '1');
                throw new \Exception('参数有问题');
            }
            // if (!$res['token']) {
            //     $socket->emit('exception', '2');
            //     throw new \Exception('2');
            // }
            return $res;
        } catch (\Exception $e) {
            $this->errorMsg($e);
        }
    }

    //用户注册
    private function userRegister($uid, $socket)
    {
        try {
            if (isset($socket->uid)) {
                return;
            }
            // 更新对应uid的在线数据
            $uid = $uid . '';
            if (!isset($this->web_clients[$uid])) {
                $this->web_clients[$uid] = 0;
            }
            // 这个uid有++$web_client[$uid]个socket连接
            ++$this->web_clients[$uid];
            // 将这个连接加入到uid分组，方便针对uid推送数据
            $socket->join("user-$uid");
            $socket->uid = $uid;
            $this->echoWord("用户 $uid 登录，当前id现有" . $this->web_clients[$uid] . "个连接。");
        } catch (\Exception $e) {
            $this->errorMsg($e);
        }
    }

    //检查box状态
    private function validateBox($did, $socket)
    {
        try {
            if (!$this->redis->exists('con_box_' . $did)) {
                $socket->emit('speech status', array('token' => "rjjgdzjmbsyzxdrlcblmzgjhwhgxtdxq", 'status' => 0, 'code' => '0000'));
                $this->echoWord("盒子 $did 未连接");
                return false;
            }
            return true;
        } catch (\Exception $e) {
            $this->errorMsg($e);
        }
    }

    private function bindBox($did, $socket)
    {
        try {
            //状态去文档看
            $status = ['1' => '0003', '2' => '0001', '3' => '0004', '4' => '0003'];
            //加入广播中
            $socket->join("box2user-$did");
            //获取设备注册状态
            $device = json_decode($this->redis->get('con_box_' . $did), true);
            //发送当前盒子状态
            $socket->emit('speech status', array('token' => "rjjgdzjmbsyzxdrlcblmzgjhwhgxtdxq", 'meeting_id' => $device['mid'], 'status' => $device['status'], 'code' => $status[$device['status']]));
            $this->echoWord("用户 $socket->uid 连接盒子" . $did);
        } catch (\Exception $e) {
            $this->errorMsg($e);
        }
    }

    private function pingBox($did, $socket)
    {
        try {
            if (!$this->redis->exists('con_box_' . $did)) {
                $socket->emit('speech pong', array('token' => "rjjgdzjmbsyzxdrlcblmzgjhwhgxtdxq", 'status' => 0, 'code' => '0000'));
                $this->echoWord("盒子 $did 未连接");
                return false;
            }
            //状态去文档看
            $status = ['1' => '0003', '2' => '0001', '3' => '0004', '4' => '0003'];
            //获取设备注册状态
            $device = json_decode($this->redis->get('con_box_' . $did), true);
            //发送当前盒子状态
            $socket->emit('speech pong', array('token' => "rjjgdzjmbsyzxdrlcblmzgjhwhgxtdxq", 'meeting_id' => $device['mid'], 'status' => $device['status'], 'code' => $status[$device['status']]));
        } catch (\Exception $e) {
            $this->errorMsg($e);
        }
    }

    private function commandBox($data, $socket)
    {
        try {
            //获取设备注册状态
            $this->io->to("user2box-" . $data['did'])->emit("boxcommand", json_encode(array("token" => "rjjgdzjmbsyzxdrlcblmzgjhwhgxtdxq", "code" => $data['code'])));
        } catch (\Exception $e) {
            $this->errorMsg($e);
        }
    }

    private function getAudioHistory($mid, $socket)
    {
        try {
            $br = [
                'or_txt' => '<br>',
                'ed_txt' => '',
                'url' => '',
                'paly_time' => 0,
                'status' => 1,
                'timestamp' => '',
                'sc' => 0
            ];
            $dispatch[] = json_encode($br);
            $count = $this->redis->hget($mid . '_state', 'lower') - 1;
            for ($i = 1; $i <= $count; $i++) {
                $result = $this->redis->hget($mid, $i);
                $result_la = $this->redis->hget($mid, $i - 1);
                $result_arr = json_decode($result, true);
                $result_la_arr = json_decode($result_la, true);

                if ($result_arr['sc'] != $result_la_arr['sc']) {
                    $br = [
                        'or_txt' => '<br>',
                        'ed_txt' => '',
                        'url' => '',
                        'paly_time' => 0,
                        'status' => 1,
                        'timestamp' => '',
                        'sc' => $result_la_arr['sc'] ? $result_la_arr['sc'] : 0
                    ];
                    $dispatch[] = json_encode($br);
                }
                $dispatch[] = $this->redis->hget($mid, $i);
            }
            //返回给连接用户的值
            $data = [
                'list' => $dispatch,
                'total' => count($dispatch)
            ];
            $socket->emit('get audio history', $data);
            $socket->join($mid);
        } catch (\Exception $e) {
            $this->errorMsg($e);
        }
    }

    private function editResult($mid, $did, $data, $socket)
    {
        //获取meeting对应表的lid
        if (!($lid = $this->redis->hGet('meeting_to_lid', $mid))) {
            throw new \Exception('修改文字未找到转写文件id');
            return;
        }

        //查找哈希 中的数据
        if (!($origin = $this->redis->hGet($mid, $data['sorting']))) {
            throw new \Exception('修改文字未找到原始数据');
            return;
        }

        $transfer = json_decode($origin, true);
        if (!$transfer['status']) {
            throw new \Exception("文字状态为未完成，禁止修改");
            return;
        }

        $transfer['ed_txt'] = $data['ed_txt'];
        $transfer['is_edit'] = 1;

        //插入hash和数据库
        $this->redis->hSet($mid, $data['sorting'], json_encode($transfer, JSON_UNESCAPED_UNICODE));

        $save = [
            "lid" => $lid,
            "editor" => $transfer['ed_txt'],
            "is_edit" => 1,
            "update_time" => time()
        ];

        $res_con = Db::name("meeting_convert")->where(['lid' => $lid, 'sorting' => $data['sorting']])->update($save);

        $res_live = Db::name("meeting_live_speech")->where('id', $lid)->update(['edit_state' => 1]);

        //盒子端同屏修改

        //获取盒子端的socket
        if ($this->redis->exists('con_box_' . $did)) {
            //获取全部信息
            $all_message = '';
            $count = $this->redis->hget($mid . '_state', 'lower') - 1;
            for ($i = $count; $i >= 1; $i--) {
                if (strlen($all_message) >= 600) {
                    break;
                }
                $datas = json_decode($this->redis->hget($mid, $i), true);
                $str = $datas['ed_txt'] ? trim($datas['ed_txt']) : trim($datas['or_txt']);
                if (strlen($str . $all_message) >= 2048) {
                    break;
                }
                $last_datas = json_decode($this->redis->hget($mid, $i - 1), true);
                if ($datas['sc'] != $last_datas['sc']) {
                    $all_message = ("\n\t" . $str) . $all_message;
                    continue;
                }
                $all_message = $str . $all_message;
            }
            if ($n_pos = strpos($all_message, "\n") === 0) {
                $all_message = substr($all_message, $n_pos);
            }
            $this->io->to('user2box-' . $did)->emit('boxedit', $all_message);
        }
        //返回
        $data = [
            "code" => 200,
            "message" => "修改成功"
        ];
        $socket->emit('edit', $data);
    }

    //盒子事件

    //盒子响应
    private function responseBox($data)
    {

        try {
            var_dump('res-status-' . $data['status'] . '-mid-' . $data['mid']);
            $device = json_decode($this->redis->get('con_box_' . $data['did']), true);
            //如果status是5代表刚登陆，判断是否存在disconnec的数据
            if ($device['status'] == 5) {
                if ($this->redis->exists('dis_box_' . $data['did'])) {
                    $dis = json_decode($this->redis->get('dis_box_' . $data['did']), true);
                    //如果当前mid和之前断开的mid不同，则结束上一次
                    if ($dis['mid'] != $data['mid']) {
                        $this->endSpeech($dis['mid']);
                    }
                    //删除断开连接的状态
                    $this->redis->del('dis_box_' . $data['did']);
                    //在断开队列删除原状态
                    $this->redis->hDel('disconnection', $data['did']);
                }
            }

            $device['mid'] = $data['mid'];
            //如果之前状态为暂停，该状态为继续
            if ($device['status'] == 3 && $data['status'] == 1) {
                $device['status'] = 4;
            } else {
                $device['status'] = $data['status'];
            }
            //如果之前现在状态为结束，则mid为空
            // if ($device['status'] == 2) {
            //     $device['mid'] = '';
            // }
            $this->redis->set('con_box_' . $data['did'], json_encode($device));
            //状态去文档看
            $status = ['1' => '0003', '2' => '0001', '3' => '0004', '4' => '0003'];
            $this->io->to("box2user-" . $data['did'])->emit('speech status', array('token' => "rjjgdzjmbsyzxdrlcblmzgjhwhgxtdxq", 'meeting_id' => $device['mid'], 'status' => $device['status'], 'code' => $status[$device['status']]));
        } catch (\Exception $e) {
            $this->errorMsg($e);
        }
    }

    //盒子登录
    private function loginBox($did, $socket)
    {
        try {
            //重新登录 如果存在之前的con状态则将其置为断开状态
            if ($this->redis->exists('con_box_' . $did)) {
                //更改原数据名字
                $this->redis->rename('con_box_' . $did, 'dis_box_' . $did);
            }
            $device = [
                'mid' => '',
                'status' => 5,
                'timestamp' => time(),
                'sgin' => uniqid($did . time())
            ];
            $result = Db::name('equipment')->where('equipmentid', $did)->update(['connection' => 1, 'update_time' => time()]);
            $socket->did = $did;
            $socket->sgin = $device['sgin'];
            //加入配置队列
            $this->redis->set('con_box_' . $did, json_encode($device));
            var_dump('login');
            $socket->join("user2box-" . $did);
            $socket->emit('box_status', array('token' => "rjjgdzjmbsyzxdrlcblmzgjhwhgxtdxq"));
            $this->echoWord("盒子 $did 登录");
        } catch (\Exception $e) {
            echo $e;
        }
    }

    private function logoutBox($did, $socket)
    {
        try {
            $result = Db::name('equipment')->where('equipmentid', $did)->update(['connection' => 2, 'update_time' => time()]);
            $this->redis->del('con_box_' . $did);
            $this->io->to("box2user-" . $socket->did)->emit('speech status', array('token' => "rjjgdzjmbsyzxdrlcblmzgjhwhgxtdxq", 'status' => 0, 'code' => '0000'));
            $socket->disconnect();
            $this->echoWord("盒子 $did 登出");
        } catch (\Exception $e) {
            $this->errorMsg($e);
        }
    }

    private function fileListBox($data, $socket)
    {
        try {
            $list = json_decode($data['filelist'], true);

            $filelist = array();
            foreach ($list as $meeting_id) {
                $result = Db::name('meeting_live_speech')->where('meeting_tag', $meeting_id)->field("id,transition_state,filename,is_manual")->find();
                if (!$result) {
                    $filelist[$meeting_id] = [
                        "filename" => '',
                        "transtatus" => '',
                        "transpersent" => ''
                    ];
                } else {
                    $filename = $result['filename'];
                    if ($result['is_manual'] == 1) {
                        $filename = $filename . '.pcm';
                    }
                    $filelist[$meeting_id] = [
                        "filename" => $filename,
                        "transtatus" => $result['transition_state'] == 2 ? 2 : 1,
                        "transpersent" => $result['transition_state'] == 2 ? "100%" : "0%"
                    ];
                }
            }
            $return = [
                'token' => $data['token'],
                'filelist' => $filelist
            ];

            $return = json_encode($return);
            $socket->emit('boxtranstatus', $return);
        } catch (\Exception $e) {
            $this->errorMsg($e);
        }
    }

    public function echoWord($string)
    {
        echo date('Y-m-d H:i:s', time()) . " $string\n";
    }

    public function errorMsg($e)
    {
        $msg = 'Error:' . $e->getMessage() . "\n";
        $msg .= $e->getTraceAsString() . "\n";
        $msg .= '异常行号：' . $e->getLine() . "\n";
        echo date('Y-m-d H:i:s', time()) . " $msg\n";
    }

    public function endSpeech($mid)
    {
        try {
            $speech = LiveSpeech1::get(['meeting_tag' => $mid]);
            if ($speech) {
                if ($speech->transition_state == 4) {
                    $arr = [
                        'id' => $speech->id
                    ];
                    $this->redis->lPush('mergeQueue', json_encode($arr));
                }
            }
        } catch (\Exception $e) {
            $this->errorMsg($e);
        }
    }

    //检查断开连接的音频
    public function checkDisconnection()
    {
        $list = $this->redis->hGetAll('disconnection');
        if (!$list) {
            return;
        }
        foreach ($list as $did => $mid) {
            if (!$this->redis->exists('dis_box_' . $did)) {
                $this->endSpeech($mid);
                $this->redis->hDel('disconnection', $did);
            }
        }
    }
}
